package concurrecy.performance;

import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 测试锁的性能
 */
abstract class Accumulator {
    public static int cycles = 5000;
    protected final static int N = 8;
    protected long value = 1;
    protected String id;
    private final ExecutorService service = Executors.newFixedThreadPool(N * 2);
    protected final CountDownLatch latch = new CountDownLatch(N * 2);

    protected static long[] preLoadArray = new long[1024];
    protected final int index = new Random().nextInt(preLoadArray.length);

    static {
        Random random = new Random();
        for (int i = 0; i < preLoadArray.length; i++) {
            preLoadArray[i] = random.nextLong();
        }
    }

    private long duration;

    protected abstract void calc();
    protected abstract double read();

    private class Modifier implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < cycles; i++) {
                calc();
            }
            latch.countDown();
        }
    }

    private class Reader implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < cycles; i++) {
                double d = read();
            }
            latch.countDown();
        }
    }

    public void test() {
        long start = System.nanoTime();
        for (int i = 0; i < N; i++) {
            service.execute(new Modifier());
            service.execute(new Reader());
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        service.shutdown();
        long end = System.nanoTime();
        duration = end - start;
        System.out.printf("%-10s : %d\n", id, duration);
    }

    public static void report(Accumulator a1, Accumulator a2) {
        System.out.printf("%-10s / %-10s : %.2f\n", a1.id, a2.id, ((double)a1.duration) / a2.duration);
    }

    static class BaseLine extends Accumulator {
        {
            id = "BaseLine";
        }
        @Override
        protected void calc() {
            value += preLoadArray[index];
        }

        @Override
        protected double read() {
            return value;
        }
    }

    static class SyncTest extends Accumulator {
        {id = "SyncTest";}
        @Override
        protected synchronized void calc() {
            value += preLoadArray[index];
        }

        @Override
        protected synchronized double read() {
            return value;
        }
    }

    static class LockTest extends Accumulator {
        {id = "LockTest";}
        private Lock lock = new ReentrantLock();
        @Override
        protected void calc() {
            lock.lock();
            try {
                value += preLoadArray[index];
            } finally {
                lock.unlock();
            }
        }

        @Override
        protected double read() {
            lock.lock();
            try {
                return value;
            } finally {
                lock.unlock();
            }
        }
    }

    static class AtomicTest extends Accumulator {
        {id = "AtomicTest";}
        private AtomicLong value = new AtomicLong();
        @Override
        protected void calc() {
            value.addAndGet(preLoadArray[index]);
        }

        @Override
        protected double read() {
            return value.get();
        }
    }
}

public class TestLock {
    static void test() {
        Accumulator.BaseLine baseLine = new Accumulator.BaseLine();
        Accumulator.SyncTest syncTest = new Accumulator.SyncTest();
        Accumulator.LockTest lockTest = new Accumulator.LockTest();
        Accumulator.AtomicTest atomicTest = new Accumulator.AtomicTest();

        System.out.printf("======== Cycles: %d ===========\n", Accumulator.cycles);
        baseLine.test();
        syncTest.test();
        lockTest.test();
        atomicTest.test();
        System.out.println();

        Accumulator.report(syncTest, baseLine);
        Accumulator.report(lockTest, baseLine);
        Accumulator.report(atomicTest, baseLine);
        System.out.println();
        System.out.println();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 7; i++) {
            test();
            Accumulator.cycles *= 2;
        }
    }
}
